package org.dromara.common.satoken.core.dao

import cn.dev33.satoken.dao.SaTokenDao
import cn.dev33.satoken.dao.auto.SaTokenDaoBySessionFollowObject
import cn.dev33.satoken.util.SaFoxUtil
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import org.dromara.common.redis.utils.RedisUtils
import org.dromara.common.redis.utils.RedisUtils.expire
import org.dromara.common.redis.utils.RedisUtils.getCacheObject
import org.dromara.common.redis.utils.RedisUtils.getTimeToLive
import org.dromara.common.redis.utils.RedisUtils.hasKey
import org.dromara.common.redis.utils.RedisUtils.keys
import org.dromara.common.redis.utils.RedisUtils.setCacheObject
import java.time.Duration
import java.util.concurrent.TimeUnit

/**
 * Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
 * <p>
 * 采用 caffeine + redis 多级缓存 优化并发查询效率
 * <p>
 * SaTokenDaoBySessionFollowObject 是 SaTokenDao 子集简化了session方法处理
 *
 * @author Lion Li
 * @updater LikeYouDo
 * @date 2025/1/10 08:44
 */
open class PlusSaTokenDao : SaTokenDaoBySessionFollowObject {
    companion object {
        private val CAFFEINE: Cache<String, Any?> = Caffeine.newBuilder() // 设置最后一次写入或访问后经过固定时间过期
            .expireAfterWrite(5, TimeUnit.SECONDS) // 初始的缓存空间大小
            .initialCapacity(100) // 缓存的最大条数
            .maximumSize(1000)
            .build()
    }

    /**
     * 获取Value，如无返空
     */
    override fun get(key: String): String? {
        val o = CAFFEINE[key, { _ -> getCacheObject(key) }]
        return o as String?
    }

    /**
     * 写入Value，并设定存活时间 (单位: 秒)
     */
    override fun set(key: String, value: String, timeout: Long) {
        if (timeout == 0L || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
            return
        }
        // 判断是否为永不过期
        if (timeout == SaTokenDao.NEVER_EXPIRE) {
            setCacheObject(key, value)
        } else {
            setCacheObject(key, value, Duration.ofSeconds(timeout))
        }
        CAFFEINE.invalidate(key)
    }

    /**
     * 修修改指定key-value键值对 (过期时间不变)
     */
    override fun update(key: String, value: String) {
        if (hasKey(key)) {
            setCacheObject(key, value, true)
            CAFFEINE.invalidate(key)
        }
    }

    /**
     * 删除Value
     */
    override fun delete(key: String) {
        if (RedisUtils.deleteObject(key)) {
            CAFFEINE.invalidate(key)
        }
    }

    /**
     * 获取Value的剩余存活时间 (单位: 秒)
     */
    override fun getTimeout(key: String): Long {
        val timeout = getTimeToLive<Any>(key)
        // 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
        return if (timeout < 0) timeout else timeout / 1000 + 1
    }

    /**
     * 修改Value的剩余存活时间 (单位: 秒)
     */
    override fun updateTimeout(key: String, timeout: Long) {
        expire(key, Duration.ofSeconds(timeout))
    }


    /**
     * 获取Object，如无返空
     */
    override fun getObject(key: String): Any? {
        val o = CAFFEINE[key, { _ -> getCacheObject(key) }]
        return o
    }

    /**
     * 获取 Object (指定反序列化类型)，如无返空
     *
     * @param key 键名称
     * @return object
     */
    @Suppress("UNCHECKED_CAST")
    override fun <T> getObject(key: String, classType: Class<T>): T {
        val o = CAFFEINE.get(key) { _ -> getCacheObject(key) }
        return o as T
    }

    /**
     * 写入Object，并设定存活时间 (单位: 秒)
     */
    override fun setObject(key: String, obj: Any, timeout: Long) {
        if (timeout == 0L || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
            return
        }
        // 判断是否为永不过期
        if (timeout == SaTokenDao.NEVER_EXPIRE) {
            setCacheObject(key, obj)
        } else {
            setCacheObject(key, obj, Duration.ofSeconds(timeout))
        }
        CAFFEINE.invalidate(key)
    }

    /**
     * 更新Object (过期时间不变)
     */
    override fun updateObject(key: String, obj: Any) {
        if (hasKey(key)) {
            setCacheObject(key, obj, true)
            CAFFEINE.invalidate(key)
        }
    }

    /**
     * 删除Object
     */
    override fun deleteObject(key: String) {
        if (RedisUtils.deleteObject(key)) {
            CAFFEINE.invalidate(key)
        }
    }

    /**
     * 获取Object的剩余存活时间 (单位: 秒)
     */
    override fun getObjectTimeout(key: String): Long {
        val timeout = getTimeToLive<Any>(key)
        // 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
        return if (timeout < 0) timeout else timeout / 1000 + 1
    }

    /**
     * 修改Object的剩余存活时间 (单位: 秒)
     */
    override fun updateObjectTimeout(key: String, timeout: Long) {
        expire(key, Duration.ofSeconds(timeout))
    }

    /**
     * 搜索数据
     */
    @Suppress("UNCHECKED_CAST")
    override fun searchData(prefix: String, keyword: String, start: Int, size: Int, sortType: Boolean): List<String> {
        val keyStr = "$prefix*$keyword*"
        return CAFFEINE[keyStr, {
            val keys = keys(keyStr)
            val list: List<String> = ArrayList(keys)
            SaFoxUtil.searchList(list, start, size, sortType)
        }] as List<String>
    }
}
